﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Objects;
using System.Xml;
using System.Data.Metadata.Edm;
using System.Data.Mapping;
using System.Reflection;
using System.IO;
using System.Data.Common;
using System.Globalization;
using System.Data.Linq;
using System.Data.EntityClient;
using System.Xml.Linq;

namespace SlamCms.SharePoint.Data
{
	public static class ObjectContextExtension
	{
		private static Dictionary<string, XElement> _ssdlElements = new Dictionary<string, XElement>();
		private static Dictionary<string, XElement> _mslElements = new Dictionary<string, XElement>();

		public static void RegisterMetadataWorkspaceItemCollections(this ObjectContext context, string modelName)
		{	
			//Presumably the EF models are defined in the same assembly as the ObjectContext
			//class corresponding to them.
			if (!_ssdlElements.ContainsKey(modelName))
			{
				using (XmlReader ssdlReader = XmlReader.Create(context.GetType().Assembly.GetManifestResourceStream(modelName + ".ssdl")))
				{
					_ssdlElements.Add(modelName, XElement.Load(ssdlReader));
				}
			}

			if (!_mslElements.ContainsKey(modelName))
			{
				using (XmlReader mslReader = XmlReader.Create(context.GetType().Assembly.GetManifestResourceStream(modelName + ".msl")))
				{
					_mslElements.Add(modelName, XElement.Load(mslReader));
				}
			}

			XElement ssdlElement = null;
			XElement mslElement = null;
			if (_ssdlElements.ContainsKey(modelName))
			{
				ssdlElement = _ssdlElements[modelName];
			}

			if (_mslElements.ContainsKey(modelName))
			{
				mslElement = _mslElements[modelName];
			}

			ItemCollection sSpaceCollection = null;
			if (ssdlElement != null && mslElement != null && !context.MetadataWorkspace.TryGetItemCollection(DataSpace.SSpace, out sSpaceCollection))
			{
				StoreItemCollection storeItemCollection = new StoreItemCollection(new XmlReader[] { ssdlElement.CreateReader() });
				EdmItemCollection editItemCollection = context.MetadataWorkspace.GetItemCollection(DataSpace.CSpace) as EdmItemCollection;

				StorageMappingItemCollection storageMappingItemCollection = new StorageMappingItemCollection(editItemCollection, storeItemCollection, new XmlReader[] { mslElement.CreateReader() });

				context.MetadataWorkspace.RegisterItemCollection(storeItemCollection);
				context.MetadataWorkspace.RegisterItemCollection(storageMappingItemCollection);
			}
		}

		public static void SetStoreColumnNameForEntityProperty(this ObjectContext context, string modelName, string entitySetName, string propertyName, string columnName)
		{
			//Presumably the EF models are defined in the same assembly as the ObjectContext
			//class corresponding to them.
			if (!_ssdlElements.ContainsKey(modelName))
			{
				using (XmlReader ssdlReader = XmlReader.Create(context.GetType().Assembly.GetManifestResourceStream(modelName + ".ssdl")))
				{
					_ssdlElements.Add(modelName, XElement.Load(ssdlReader));
				}
			}

			if (!_mslElements.ContainsKey(modelName))
			{
				using (XmlReader mslReader = XmlReader.Create(context.GetType().Assembly.GetManifestResourceStream(modelName + ".msl")))
				{
					_mslElements.Add(modelName, XElement.Load(mslReader));
				}
			}

			XElement mslElement = _mslElements[modelName];
			mslElement.Descendants(XName.Get("EntitySetMapping", mslElement.Name.NamespaceName)).Where(esm => esm.Attribute("Name").Value == entitySetName)
				.Descendants(XName.Get("ScalarProperty", mslElement.Name.NamespaceName)).Where(sp => sp.Attribute("Name").Value == propertyName).FirstOrDefault()
				.SetAttributeValue("ColumnName", columnName);

			_mslElements[modelName] = mslElement;
		}

		public static void SetStoreSchema(this ObjectContext context, string modelName, string schemaName)
		{
			//Presumably the EF models are defined in the same assembly as the ObjectContext
			//class corresponding to them.
			if (!_ssdlElements.ContainsKey(modelName))
			{
				using (XmlReader ssdlReader = XmlReader.Create(context.GetType().Assembly.GetManifestResourceStream(modelName + ".ssdl")))
				{
					_ssdlElements.Add(modelName, XElement.Load(ssdlReader));
				}
			}

			if (!_mslElements.ContainsKey(modelName))
			{
				using (XmlReader mslReader = XmlReader.Create(context.GetType().Assembly.GetManifestResourceStream(modelName + ".msl")))
				{
					_mslElements.Add(modelName, XElement.Load(mslReader));
				}
			}
			context.RegisterMetadataWorkspaceItemCollections(modelName);

			EntityContainer container = context.MetadataWorkspace.GetItems<EntityContainer>(DataSpace.SSpace).FirstOrDefault();
			foreach (EntitySetBase entitySet in container.BaseEntitySets)
			{
				FieldInfo schemaField = typeof(EntitySetBase).GetField("_schema", BindingFlags.NonPublic | BindingFlags.Instance);
				object schemaValue = schemaField.GetValue(entitySet);
				if (schemaValue != null && !schemaValue.ToString().Equals(schemaName, StringComparison.OrdinalIgnoreCase) && !schemaField.GetValue(entitySet).ToString().Equals("SLAM", StringComparison.OrdinalIgnoreCase))
				{
					schemaField.SetValue(entitySet, schemaName);
				}
			}
		}

		public static int ExecuteStoreCommand(this ObjectContext context, string commandText, params object[] parameters)
		{
			//For now this will not really use EF, the intention is to do here what EF 4 does in this method
			EntityConnectionStringBuilder entityConnectionString = new EntityConnectionStringBuilder(context.Connection.ConnectionString);
			DataContext dataContext = new DataContext(entityConnectionString.ProviderConnectionString);
			return dataContext.ExecuteCommand(commandText, parameters);
		}

		public static IEnumerable<TElement> ExecuteStoreQuery<TElement>(this ObjectContext context, string commandText, params object[] parameters)
		{
			//For now this will not really use EF, the intention is to do here what EF 4 does in this method
			EntityConnectionStringBuilder entityConnectionString = new EntityConnectionStringBuilder(context.Connection.ConnectionString);
			DataContext dataContext = new DataContext(entityConnectionString.ProviderConnectionString);
			return dataContext.ExecuteQuery<TElement>(commandText, parameters);
		}
	}
}
